/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component;

import robocup.component.infotypes.PlayerParamInfo;

/**
 * The class PConf (Player-Configuarion) captures all player specific parameters,
 * which can occur in a game. This includes parameter for heterogeneous player 
 * types. It implements the <code>Comparable</code>-Interface in order to sort
 * the configurations by different heterogeneous attributes, e.g. dash-power
 * of kiackable margin. This is important for the coach, which assigns these
 * types to agents.
 * <p> Note that changing these values here does not affect the simulation itself,
 * but the way an agent thinks about the simulation. E.g. if an agent changes his
 * dash-power-rate here, he thinks that he dashes faster, but he doesn't, because
 * simulation is done by the server. Note that also the player types of the
 * opponent team is not known and hence always type 0, which is almost always
 * wrong (only goalies always have type 0), because the other types are faster,
 * have better dribbling skills (kickable margin) or turn faster (inertia 
 * moment). 
 */
public class PConf implements Comparable<PConf> {

	/**
	 * This enumeration holds all heterogeneous attributes, which can be 
	 * compared. 
	 */
	public enum COMPARE_TYPE {
		SPEED, INERTIA, KICKABLE_DISTANCE, DASH_POWER, KICKABLE_AND_DASH_POWER_AND_STAMINA, DASH_POWER_AND_STAMINA, KICKABLE_AND_INERTIA, DASH_POWER_AND_DOUBLE_STAMINA, DOUBLE_DASH_POWER_AND_STAMINA, STAMINA, STAMINA_AND_KICKABLE
	}

	/**
	 * Field which says how to compare PConfs!
	 */
	private COMPARE_TYPE compareType = COMPARE_TYPE.SPEED;

	/**
	 * A player's max acceleration
	 */
	public double PLAYER_ACCEL_MAX = 1.0;

	/**
	 * A player's max speed
	 */
	public double PLAYER_SPEED_MAX = 1.2;

	/**
	 * A player's max stamina increase
	 */
	public double STAMINA_INC_MAX = 45.0;

	/**
	 * The rate of deceleration
	 */
	public double PLAYER_DECAY = 0.4;

	/**
	 * A player's intertia for turning turning_angel = moment /
	 * (1+inertia_moment*speed)
	 */
	public double INERTIA_MOMENT = 5.0;

	/**
	 * Base power of a dash, effectiv dash power is effort*dash_power_rate*power
	 */
	public double DASH_POWER_RATE = 0.006;

	/**
	 * A player's radius.
	 */
	public double PLAYER_SIZE = 0.3;

	/**
	 * A player is able to kick the ball within this radius.
	 */
	public double KICKABLE_MARGIN = 0.7;

	/**
	 * Noise added to a kick
	 */
	public double KICK_RAND = 0.0;

	/**
	 * If there's not enough stamina for a dash, player will use a bit of extra
	 * stamina
	 */
	public double EXTRA_STAMINA = 0.0;

	/**
	 * max player dash effort
	 */
	public double EFFORT_MAX = 1.0;

	/**
	 * min player dash effort
	 */
	public double EFFORT_MIN = 0.6;

	/**
	 * The id of the heterogeneous player type defined in and received
	 * from the server. The default player type (hence also the goalie)
	 * is id 0. All default values given above refer to type 0. The others
	 * (usually type 1 to 6) have different strengths and weaknesses.
	 */
	private int id = 0;

	/**
	 * The effective max-speed is defined in getEffectiveMaxSpeed. For type 0,
	 * this is 1, all others depend on the player types dash-power, its decay
	 * and the max-effort. Nevertheless, no player can run faster than 
	 * player-max-speed, which is 1.2 in the usual setting.
	 */
	private double effectiveMaxSpeed = -1;

	/**
	 * The maximum distance, a player can move after three cycles, assuming he
	 * has no current speed.
	 */
	private double moveDist = -1;

	/**
	 * A flag indicating the comparing attributes should be normalized
	 * within their max and min-values. This is used within the compare
	 * function.
	 */
	private boolean NORMALIZE = true;

	/**
	 * Reference to the info-object, which contains the PConf.
	 */
	private PlayerParamInfo ppi = null;

	/**
	 * An object that contains the heterogeneous attributes of a player, e.g.
	 * his dash-power. This constructor creates an object with default values
	 * (type 0).
	 */
	public PConf() {

	}

	/**
	 * Sets the player-types maximum speed
	 * @param psm - the new speed
	 */
	public void setPlayerSpeedMax(double psm) {
		this.PLAYER_SPEED_MAX = psm;
	}

	/**
	 * Sets the player-types stamina increase-value.
	 * @param sim - the new stamina increase-value
	 */
	public void setStaminaInxMax(double sim) {
		this.STAMINA_INC_MAX = sim;
	}

	/**
	 * Sets the player-types decay.
	 * @param pd - the new player-decay.
	 */
	public void setPlayerDecay(double pd) {
		this.PLAYER_DECAY = pd;
	}

	/**
	 * Sets the player-types inertia.
	 * @param im - the new inertia moment
	 */
	public void setInertiaMoment(double im) {
		this.INERTIA_MOMENT = im;
	}

	/**
	 * Sets the player-types dash-power
	 * @param dpr - the new dash-power
	 */
	public void setDashPowerRate(double dpr) {
		this.DASH_POWER_RATE = dpr;
	}

	/**
	 * Sets the player-types size
	 * @param ps - the new player size.
	 */
	public void setPlayerSize(double ps) {
		this.PLAYER_SIZE = ps;
	}

	/**
	 * Sets the player-types kickable margin
	 * @param km - the new kickable margin
	 */
	public void setKickableMargin(double km) {
		this.KICKABLE_MARGIN = km;
	}

	/**
	 * Sets the random error for kicking
	 * @param kr - the new error value
	 */
	public void setKickRand(double kr) {
		this.KICK_RAND = kr;
	}

	/**
	 * Sets the player-types extra-stamina
	 * @param es - the new extra stamina
	 */
	public void setExtraStamina(double es) {
		this.EXTRA_STAMINA = es;
	}

	/**
	 * Sets the player-types maximal effort
	 * @param em - the new maximal effort
	 */
	public void setEffortMax(double em) {
		this.EFFORT_MAX = em;
	}


	/**
	 * Sets the player types minimal effort
	 * @param em - the new minimal effort
	 */
	public void setEffortMin(double em) {
		this.EFFORT_MIN = em;
	}

	/**
	 * Retrieves the player types id [0..6]
	 * @return - the id
	 */
	public int getId() {
		return this.id;
	}

	/**
	 * Sets the player-types id
	 * @param id - the id of the player-type, defined by the server
	 */
	public void setId(int id) {
		this.id = id;
	}

	/**
	 * This method retrieves the maximum speed a player can reach when only
	 * dashing
	 * 
	 * @return
	 */
	public double getEffectiveMaxSpeed() {
		if (this.effectiveMaxSpeed < 0) {
			this.effectiveMaxSpeed = Math
					.min((DASH_POWER_RATE * EFFORT_MAX * 100.0)
							/ (1 - PLAYER_DECAY), this.PLAYER_SPEED_MAX);
		}
		return effectiveMaxSpeed;
	}

	/**
	 * This method retrieves the maximum move distance after a fixed number of
	 * cycles when not in motion
	 * 
	 * @return the distance moved
	 */
	public double getMaxMoveDist() {
		if (moveDist < 0) {
			this.moveDist = DASH_POWER_RATE * EFFORT_MAX * 100.0
					* (3 + 2 * PLAYER_DECAY + (PLAYER_DECAY * PLAYER_DECAY));
		}
		return moveDist;
	}

	/**
	 * Lists the parameters in a readable way!
	 */
	@Override
	public String toString() {
		StringBuffer buff = new StringBuffer();
		buff.append(this.id);
		buff.append(' ');
		buff.append(this.PLAYER_ACCEL_MAX);
		buff.append(' ');
		buff.append(this.PLAYER_SPEED_MAX);
		buff.append(' ');
		buff.append(this.STAMINA_INC_MAX);
		buff.append(' ');
		buff.append(this.PLAYER_DECAY);
		buff.append(' ');
		buff.append(this.INERTIA_MOMENT);
		buff.append(' ');
		buff.append(this.DASH_POWER_RATE);
		buff.append(' ');
		buff.append(this.PLAYER_SIZE);
		buff.append(' ');
		buff.append(this.KICKABLE_MARGIN);
		buff.append(' ');
		buff.append(this.KICK_RAND);
		buff.append(' ');
		buff.append(this.EXTRA_STAMINA);
		buff.append(' ');
		buff.append(this.EFFORT_MAX);
		buff.append(' ');
		buff.append(this.EFFORT_MIN);
		return buff.toString();
	}

	/**
	 * Clones this PConf object and returns it
	 * 
	 * @return a clone of this object!
	 */
	public PConf cloned() {
		PConf p = new PConf();
		p.DASH_POWER_RATE = this.DASH_POWER_RATE;
		p.EFFORT_MAX = this.EFFORT_MAX;
		p.EFFORT_MIN = this.EFFORT_MIN;
		p.EXTRA_STAMINA = this.EXTRA_STAMINA;
		p.INERTIA_MOMENT = this.INERTIA_MOMENT;
		p.KICK_RAND = this.KICK_RAND;
		p.KICKABLE_MARGIN = this.KICKABLE_MARGIN;
		p.PLAYER_ACCEL_MAX = this.PLAYER_ACCEL_MAX;
		p.PLAYER_DECAY = this.PLAYER_DECAY;
		p.PLAYER_SIZE = this.PLAYER_SIZE;
		p.PLAYER_SPEED_MAX = this.PLAYER_SPEED_MAX;
		p.STAMINA_INC_MAX = this.STAMINA_INC_MAX;
		p.id = this.id;
		return p;
	}

	/**
	 * This method compares PConfs by an a-priori defined criteria.
	 * It is used to sort PConfs by a given attribute, and then assign specific
	 * ones to certain agents.
	 * @param the
	 *            PConf to compare to
	 * @return the compare-value!
	 */
	public int compareTo(PConf other) {

		double thisWeight = (this.getEffectiveMaxSpeed() < 1.01) ? (0.1 / 1.2)
				: ((this.getEffectiveMaxSpeed() < 1.19) ? (0.5 / 1.2) : (this
						.getEffectiveMaxSpeed() / 1.2));

		double otherWeight = (other.getEffectiveMaxSpeed() < 1.01) ? (0.1 / 1.2)
				: ((other.getEffectiveMaxSpeed() < 1.19) ? (0.5 / 1.2) : (other
						.getEffectiveMaxSpeed() / 1.2));

		if (NORMALIZE)
			switch (compareType) {
			case INERTIA:
				thisWeight /= this.INERTIA_MOMENT;
				otherWeight /= other.INERTIA_MOMENT;
				break;
			case KICKABLE_DISTANCE:
				thisWeight *= this.KICKABLE_MARGIN;
				otherWeight *= other.KICKABLE_MARGIN;
				break;
			case SPEED:
				thisWeight *= this.getMaxMoveDist();
				otherWeight *= other.getMaxMoveDist();
				break;
			case STAMINA:
				thisWeight *= (this.EXTRA_STAMINA + this.normalize(25, 45,
						this.STAMINA_INC_MAX)) / 2;
				otherWeight *= (other.EXTRA_STAMINA + other.normalize(25, 45,
						other.STAMINA_INC_MAX)) / 2;
				break;
			case STAMINA_AND_KICKABLE:
				thisWeight *= (((this.EXTRA_STAMINA + this.normalize(25, 45,
						this.STAMINA_INC_MAX)) / 2) + this.normalize(
						this.ppi.KICKABLE_MARGIN_MIN,
						this.ppi.KICKABLE_MARGIN_MAX, this.KICKABLE_MARGIN));
				otherWeight *= (((other.EXTRA_STAMINA + other.normalize(25, 45,
						other.STAMINA_INC_MAX)) / 2) + other.normalize(
						other.ppi.KICKABLE_MARGIN_MIN,
						other.ppi.KICKABLE_MARGIN_MAX, other.KICKABLE_MARGIN));
				break;
			case DASH_POWER:
				thisWeight *= this.DASH_POWER_RATE;
				otherWeight *= other.DASH_POWER_RATE;
				break;
			case DASH_POWER_AND_STAMINA:
				thisWeight *= (this.normalize(this.ppi.DASH_POWER_RATE_MIN,
						this.ppi.DASH_POWER_RATE_MAX, this.DASH_POWER_RATE) + ((this.EXTRA_STAMINA + this
						.normalize(25, 45, this.STAMINA_INC_MAX)) / 2));
				otherWeight *= (other.normalize(other.ppi.DASH_POWER_RATE_MIN,
						other.ppi.DASH_POWER_RATE_MAX, other.DASH_POWER_RATE) + (((other.EXTRA_STAMINA + other
						.normalize(25, 45, other.STAMINA_INC_MAX)) / 2)));
				break;
			case DASH_POWER_AND_DOUBLE_STAMINA:
				thisWeight *= (this.normalize(this.ppi.DASH_POWER_RATE_MIN,
						this.ppi.DASH_POWER_RATE_MAX, this.DASH_POWER_RATE)
						+ this.EXTRA_STAMINA + this.normalize(25, 45,
						this.STAMINA_INC_MAX));
				otherWeight *= (other.normalize(other.ppi.DASH_POWER_RATE_MIN,
						other.ppi.DASH_POWER_RATE_MAX, other.DASH_POWER_RATE)
						+ other.EXTRA_STAMINA + other.normalize(25, 45,
						other.STAMINA_INC_MAX));
				break;
			case DOUBLE_DASH_POWER_AND_STAMINA:
				thisWeight *= (this.normalize(this.ppi.DASH_POWER_RATE_MIN,
						this.ppi.DASH_POWER_RATE_MAX, this.DASH_POWER_RATE) * 2 + ((this.EXTRA_STAMINA + this
						.normalize(25, 45, this.STAMINA_INC_MAX)) / 2));
				otherWeight *= (other.normalize(other.ppi.DASH_POWER_RATE_MIN,
						other.ppi.DASH_POWER_RATE_MAX, other.DASH_POWER_RATE) * 2 + ((other.EXTRA_STAMINA + other
						.normalize(25, 45, other.STAMINA_INC_MAX)) / 2));
				break;
			case KICKABLE_AND_DASH_POWER_AND_STAMINA:
				thisWeight *= (this.normalize(this.ppi.KICKABLE_MARGIN_MIN,
						this.ppi.KICKABLE_MARGIN_MAX, this.KICKABLE_MARGIN)
						+ this.normalize(this.ppi.DASH_POWER_RATE_MIN,
								this.ppi.DASH_POWER_RATE_MAX,
								this.DASH_POWER_RATE) + ((this.EXTRA_STAMINA + this
						.normalize(25, 45, this.STAMINA_INC_MAX)) / 2));
				otherWeight *= (other.normalize(other.ppi.KICKABLE_MARGIN_MIN,
						other.ppi.KICKABLE_MARGIN_MAX, other.KICKABLE_MARGIN)
						+ other.normalize(other.ppi.DASH_POWER_RATE_MIN,
								other.ppi.DASH_POWER_RATE_MAX,
								other.DASH_POWER_RATE) + ((other.EXTRA_STAMINA + other
						.normalize(25, 45, other.STAMINA_INC_MAX)) / 2));
				break;
			case KICKABLE_AND_INERTIA:
				thisWeight *= (this.normalize(this.ppi.KICKABLE_MARGIN_MIN,
						this.ppi.KICKABLE_MARGIN_MAX, this.KICKABLE_MARGIN) + this
						.normalize(0.1, 0.2, 1 / this.INERTIA_MOMENT));
				otherWeight *= (other.normalize(other.ppi.KICKABLE_MARGIN_MIN,
						other.ppi.KICKABLE_MARGIN_MAX, other.KICKABLE_MARGIN) + other
						.normalize(0.1, 0.2, 1 / other.INERTIA_MOMENT));
				break;
			}
		else
			switch (compareType) {
			case INERTIA:
				thisWeight /= this.INERTIA_MOMENT;
				otherWeight /= other.INERTIA_MOMENT;
				break;
			case KICKABLE_DISTANCE:
				thisWeight *= this.KICKABLE_MARGIN;
				otherWeight *= other.KICKABLE_MARGIN;
				break;
			case SPEED:
				thisWeight *= this.getMaxMoveDist();
				otherWeight *= other.getMaxMoveDist();
				break;
			case STAMINA:
				thisWeight *= this.EXTRA_STAMINA * this.STAMINA_INC_MAX;
				otherWeight *= other.EXTRA_STAMINA * other.STAMINA_INC_MAX;
				break;
			case STAMINA_AND_KICKABLE:
				thisWeight *= this.EXTRA_STAMINA * this.STAMINA_INC_MAX
						* this.KICKABLE_MARGIN;
				otherWeight *= other.EXTRA_STAMINA * other.STAMINA_INC_MAX
						* other.KICKABLE_MARGIN;
				break;
			case DASH_POWER:
				thisWeight *= this.DASH_POWER_RATE;
				otherWeight *= other.DASH_POWER_RATE;
				break;
			case DASH_POWER_AND_STAMINA:
				thisWeight *= this.DASH_POWER_RATE * this.STAMINA_INC_MAX
						* this.EXTRA_STAMINA;
				otherWeight *= other.DASH_POWER_RATE * other.STAMINA_INC_MAX
						* other.EXTRA_STAMINA;
				break;
			case DASH_POWER_AND_DOUBLE_STAMINA:
				thisWeight *= this.DASH_POWER_RATE
						* Math
								.pow(this.STAMINA_INC_MAX * this.EXTRA_STAMINA,
										2);
				otherWeight *= other.DASH_POWER_RATE
						* Math.pow(other.STAMINA_INC_MAX * other.EXTRA_STAMINA,
								2);
				break;
			case DOUBLE_DASH_POWER_AND_STAMINA:
				thisWeight *= Math.pow(this.DASH_POWER_RATE, 2)
						* this.STAMINA_INC_MAX * this.EXTRA_STAMINA;
				otherWeight *= Math.pow(other.DASH_POWER_RATE, 2)
						* other.STAMINA_INC_MAX * other.EXTRA_STAMINA;
				break;
			case KICKABLE_AND_DASH_POWER_AND_STAMINA:
				thisWeight *= this.KICKABLE_MARGIN * this.DASH_POWER_RATE
						* this.STAMINA_INC_MAX * this.EXTRA_STAMINA;
				otherWeight *= other.KICKABLE_MARGIN * other.DASH_POWER_RATE
						* other.STAMINA_INC_MAX * other.EXTRA_STAMINA;
				break;
			case KICKABLE_AND_INERTIA:
				thisWeight *= this.KICKABLE_MARGIN / this.INERTIA_MOMENT;
				otherWeight *= other.KICKABLE_MARGIN / other.INERTIA_MOMENT;
				break;
			}

		if (thisWeight > otherWeight)
			return -1;
		else if (thisWeight < otherWeight)
			return 1;
		return 0;
	}

	/**
	 * Normalizes a value to the interval [0,1], using the min, the max and
	 * the current value of an attribute.
	 * @param min - the minimum value 
	 * @param max - the maximum value
	 * @param value - the value that should be normalized
	 * @return - the normalized value
	 */
	private double normalize(double min, double max, double value) {
		return (value - min) * 100 / (max - min);
	}

	/**
	 * Retrieves the actual type by the PConf is currently compared, when
	 * calling the <code>compareTo</code>-method.
	 * @return - the active compare-type
	 */
	public COMPARE_TYPE getCompareType() {
		return compareType;
	}

	/**
	 * Sets the compare type to the given one.
	 * @param compareType - the new compare-type.
	 */
	public void setCompareType(COMPARE_TYPE compareType) {
		this.compareType = compareType;
	}

	/**
	 * Sets the player-param-info, which is the container for the PConf.
	 * @param ppi - the player-param-info, received from the server and
	 * created by the parser.
	 */
	public void setPlayerParamInfo(PlayerParamInfo ppi) {
		this.ppi = ppi;
	}

}
